type EventMap = Record<string, any>;

type EventKey<T extends EventMap> = string & keyof T;
type EventReceiver<T> = (params: T) => void;

interface Emitter<T extends EventMap> {
  on<K extends EventKey<T>>(
    eventName: K,
    fn: EventReceiver<T[K]>,
    scope?: any
  ): void;
  off<K extends EventKey<T>>(eventName: K, fn: EventReceiver<T[K]>): void;
  emit<K extends EventKey<T>>(eventName: K, params: T[K]): void;
}

interface Listener<T> {
  fn: EventReceiver<T>;
  scope?: any;
}

function createEmitter<T extends EventMap>(): Emitter<T> {
  const listeners: {
    [K in keyof EventMap]?: Array<Listener<EventMap[K]>>;
  } = {};

  return {
    on(key, fn, scope) {
      listeners[key] = (listeners[key] || []).concat({ fn, scope });
    },
    off(key, fn) {
      listeners[key] = (listeners[key] || []).filter((l) => l.fn !== fn);
    },
    emit(key, data) {
      (listeners[key] || []).forEach(function (listener) {
        listener.fn.call(listener.scope, data);
      });
    },
  };
}

export { EventMap, EventKey, EventReceiver, Emitter, Listener, createEmitter };
export default createEmitter;
